#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "utility.h"
#include "server.h"
#include "debug.h"
#include <string.h>
#include <signal.h>
#include <algorithm>
#include "BinarySearchTree.h"

using std::find;
using std::make_pair;
/** Jump enviroment, to hold state */
jmp_buf env;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
bool doexit = false;

/** setups server on MYPORT */
int setup_server() {
   int status, sk = 0, yes = 1;;
   // struct sockaddr_in local;
   struct addrinfo hints, *res, *p;
   struct sigaction sa;
   
   sa.sa_handler = sigint_handler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
   if( sigaction(SIGINT, &sa, NULL) == -1) {
      return -1;
   }

   memset(&hints, 0, sizeof( hints ) );
   hints.ai_family = AF_UNSPEC; //binding on ipv6
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_PASSIVE; 
   if(( status = getaddrinfo( NULL, MYPORT, &hints, &res)) != 0 ) {
      return ERROR;
   }
   for( p = res; p != NULL; p = p->ai_next ) {
      if((sk = socket( p->ai_family, p->ai_socktype, p->ai_protocol)) == -1 ) {
         continue;
      }
      if( setsockopt( sk, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))==-1) {
         return ERROR;
      }
      if( (status = bind( sk, p->ai_addr, p->ai_addrlen)) == -1 ) {
         close( sk );
         continue;
      }
      break;
   }                                   
   freeaddrinfo( res );
   if( p == NULL ) {
      return -1;
   }

   return sk;
}
void close_client( data_t & data, int32_t sk, int id ) {
   LOCK();
   DMSG("CLosing client connections");
   vector<int>::iterator it;
   logout_user( sk, id, data );
   it = find( data.vSock.begin(), data.vSock.end(), sk);
   if ( it != data.vSock.end() ) {
         data.vSock.erase(it);
   }
   data.uidsk[id] = 0;
   UNLOCK();
}
int logout_user( int sk, int id, data_t &data ) {
   send_mesg( sk, NOTICE, N_ULOGOUT,0,id,NULL);
   for( unsigned i = 0; i < data.vSock.size(); ++i ) {
      DMSG2("%u: sending logout mesg to %u",id, data.vSock[i] );
      send_mesg( data.vSock[i], NOTICE, N_OLOGOUT, 0, id, NULL );
   }
   return 0;
   
}
void* handle_client( void * v ) {
   thread_param *tp  = static_cast<thread_param*>(v);
   data_t *data = tp->d;
   int sk = tp->sk;
   int id = tp->id;
   int bytesread;
   Message m;
   vector<int>::const_iterator cit;
   bool loggedin = true;

   while( loggedin 
         && (bytesread = recv_mesg( sk, m)) > 0 ) {

      DMSG2( "%u Recv: %s", id, m.message );
      if( m.flags == N_ULOGOUT ) {
         DMSG1("%u: logout request",id);
         close_client( *data, sk, id );
         loggedin = false;
      }
      else {

         LOCK();
         for( cit = data->vSock.begin(); cit != data->vSock.end(); ++cit) {
            //Look into when to exit, don't always want to if someones dead 
            DMSG2("%u: Sending to socket %u", id, *cit );
            DMSG2("%u: Message: %s", id, m.message );
            send_mesg( *cit, m.type, m.flags, m.size, id,m.message);

         }
         UNLOCK();
      }
   }
   if( bytesread == -1 ) {
      if( close(sk) == -1 ) { perror("Child"); }
      close_client( *data, sk, id );
      pthread_exit((void*)EXIT_FAILURE); 
   }

   if( close(sk) == -1 ) { perror("Child"); }
   DMSG1("%u: exiting", id);
   close_client( *data, sk, id );
   pthread_exit((void*)EXIT_SUCCESS);

}

int find_next_uid( int uidsk[] ) {
   for( int i = 0; i < MAX_CONNECTIONS; ++i ) {
      if( uidsk[i] == 0 ) {
         return i;
      }
   }
   return -1;
}
int login_user( int sk, data_t &data, int id, const char * uname ) {
   static BinarySearchTree<int, string> uidstr;
   
   uidstr[id] = uname;

   if( send_mesg( sk, NOTICE, N_ULOGIN, strlen(uname), id, uname ) == -1 ) {
      return -1;
   }
   for( unsigned i = 0; i < MAX_CONNECTIONS; ++i ) {
      if( data.uidsk[i] != 0 ) {
         send_mesg( data.uidsk[i], NOTICE, N_OLOGIN, strlen(uname), id, uname );
         send_mesg( sk, NOTICE, N_OLOGIN, strlen(uidstr[i].c_str()), i, uidstr[i].c_str() );
      }

   }
   return 0;
}
STATES tcp_listen(int sk, data_t &data, vector<pthread_t> &threads) {

   int client, id;
   pthread_t thread;
   Message m;
   thread_param *tp = new thread_param();

   if( listen(sk, MAX_CONNECTIONS) == -1 ) {
      return ERROR;
   }
   if((client = accept( sk, (struct sockaddr*)0,(socklen_t*)0)) == -1 ) {
      return ERROR;
   }

   if( recv_mesg( client, m) == -1 ) {
      return ERROR;
   }
   m.message[UNAME_SIZE] = '\0';
   DMSG1("Login Request for %s",m.message);
   LOCK();

   if( (id = find_next_uid( data.uidsk )) == -1 ) {
      if( send_mesg( client, NOTICE, N_REFUSED,12 ,0,"Server Full\0") == -1 ) {
         DMSG("Server Full");
      }
      UNLOCK();
         perror("tcp_listen");
      return LISTEN; 
   }
   else {
      if( login_user( client, data, id, m.message) == -1 ) {
         close(client);
         delete tp;
         UNLOCK();
         perror("tcp_listen");
         return LISTEN;
      }
   }
   data.uidsk[(unsigned)id] = client;

   data.vSock.push_back( client );
   UNLOCK();
   tp->d = &data;
   tp->sk = client;
   tp->id = id;
   DMSG1("Starting child using uid %u", id);
   if( pthread_create( &thread, NULL, handle_client, (void*)tp) ) {
      return ERROR;
   }
   threads.push_back(thread);
   return LISTEN;
}
/** Sigint handler doy-ee*/
void sigint_handler( int i ) {
   doexit = true;

}                                    
